package com.huaxin.common.view;

import android.graphics.Rect;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.EditText;

import com.huaxin.common.R;
import com.huaxin.common.tools.Log;

import java.util.ArrayList;

/**
 * Created by hb on 2016/6/30.
 * A Simple Refresh Helper Class.
 * You can use like {@link RefreshFrameLayout#proxy,RefreshLinearLayout#proxy,RefreshRelativeLayout#proxy}
 */
public class Refreshable {

    //all status of getBitmap
    public final static int PULLING = 0;
    public final static int REFRESH = PULLING + 1;
    public final static int IDLE = PULLING + 2;
    public final static int WAITING = PULLING + 3;
    int status;//current getBitmap status
    int headHeight;//height of getBitmap view
    float ratio = 1.5f;//aspect of max height that allowed pulled
    View headView;//getBitmap view
    int update;//step offset of Y
    View tip;//groupIcon of getBitmap view

    ViewGroup content;//parent view

    AbsListView tag;//scrollable view(ListView,GridView,ScrollView etc.)

    float downY, downX;//touch down's pos
    boolean isViewPass;//a value which judge the view can allow to pull refresh
    ArrayList<View> filter = new ArrayList<>();//the list of view to judge

    public Refreshable(ViewGroup view) {
        content = view;
    }

    public void init() {
        if (content == null) return;
        filterView(content);
        if (tag != null) {
            tag.setSelector(R.color.transparent);
        }
        headView = LayoutInflater.from(content.getContext()).inflate(R.layout.common_view_refresh_head, content, false);
        ViewGroup.LayoutParams headParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        headView.measure(View.MeasureSpec.AT_MOST, View.MeasureSpec.UNSPECIFIED);
        //hide it first
        headHeight = headView.getMeasuredHeight();
        update = (int) (headHeight / 15f);// (headHeight / 250f) * (1000 / 60)
        content.addView(headView, headParams);
        headView.setPadding(0, -headHeight, 0, 0);
        isViewPass = true;
        Log.w("tag:" + tag + ",headHeight:" + headHeight);
        tip = headView.findViewById(R.id.head_tip);
        //tip.setPivotX(tip.getMeasuredWidth() / 2);
        //tip.setPivotX(tip.getMeasuredHeight() / 2);
    }

    public boolean process(MotionEvent ev) {
        if (MotionEvent.ACTION_DOWN == ev.getAction()) {
            isViewPass = true;
        } else {
            if (!isViewPass) return false;
        }
        //Log.w("process:" + ev);
        /**
         * if the scroll view can't scroll when scrollY > 0
         */
        if (tag == null || getScrollY(tag) <= 0) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    downX = ev.getRawX();
                    downY = ev.getRawY();
                    /**
                     * filter some view (like edit text)
                     */
                    for (View v : filter) {
                        final Rect bound = new Rect();
                        v.getGlobalVisibleRect(bound);
                        if (bound.contains((int) downX, (int) downY)) {
                            isViewPass = false;
                            break;
                        }
                    }
//                    Log.w("process ACTION_DOWN:" + isViewPass);
                    break;
                case MotionEvent.ACTION_MOVE:
                    float offsetY = ev.getRawY() - downY;
                    if (status == IDLE) {
                        if (offsetY > update) {//not zero
                            status = PULLING;
                            onPulling(true);
                        }
                    } else if (status == PULLING) {
                        /**
                         * not allowed too much
                         */
                        if (offsetY >= ratio * headHeight) {
                            downY += (offsetY - ratio * headHeight) / 2;
                            offsetY = ratio * headHeight;
                        }
                        /**
                         * first saveToDatabase it and then saveToDatabase status
                         */
                        updateHeadView(offsetY /*/ ratio*/ - headHeight);
                    }
                    break;
                case MotionEvent.ACTION_CANCEL:
                case MotionEvent.ACTION_UP:
                    float oy = ev.getRawY() - downY;
                    if (oy >= ratio * headHeight) {
                        status = REFRESH;
                    } else {
                        status = IDLE;
                    }
                    postHide(0);
                    break;
            }
        }
        return true;
    }

    // at post?
    boolean posting = false;

    private void postHide(long delayTime) {
        if (posting) return;
        posting = true;
        headView.postDelayed(runnable, delayTime);
    }

    /**
     * a runnable for saveToDatabase view
     */
    private final Runnable runnable = new Runnable() {
        @Override
        public void run() {
            final int now = headView.getPaddingTop();
            if (status == REFRESH) {
                if (now <= 0) {
                    updateHeadView(0);
                    posting = false;
                    /**
                     * here we called refresh for make sure the UI's status is fine
                     */
                    if (listener != null) {
                        listener.onRefresh();
                    } else {
                        //add for test
                        onRefreshFinish();
                    }
                    return;
                } else {
                    headView.setPadding(0, now - update, 0, 0);
                    headView.postDelayed(runnable, 1000 / 60);
                }
            }
            if (status == IDLE) {
                if (now <= -headHeight) {
                    headView.setPadding(0, -headHeight, 0, 0);
                    posting = false;
                    onPulling(false);
                } else {
                    headView.setPadding(0, now - update, 0, 0);
                    headView.postDelayed(runnable, 1000 / 60);
                }
            } else if (status == WAITING) {
                final float a = tip.getAlpha() - 0.05f;
                if (a <= 0) {
                    updateHeadView(0);
                    posting = false;
                    onPulling(false);
                } else {
                    updateHeadView(a);
                    headView.postDelayed(runnable, 1000 / 60);
                }
            }
            //headView.postInvalidate();
        }
    };

    public void onRefreshFinish() {
        status = WAITING;
        //auto delay
        postHide(1000);
    }

    public void setListener(RefreshListener listener) {
        this.listener = listener;
    }

    /**
     * 表示正在刷新，界面第一次进入时调用
     * @param listener
     */
    public void onRefreshing(RefreshListener listener){
        this.listener = listener;

    }

    RefreshListener listener;

    public interface RefreshListener {
        void onRefresh();
    }

    /**
     * First, try to find ListView in it!<br>
     * Second, try to find EditText.
     *
     * @param view parent view
     */
    private void filterView(View view) {
        if (view instanceof AbsListView) {
            if (tag != null) {
                Log.w(content + " has so many scrollable view!maybe cause bug here!");
            }
            tag = (AbsListView) view;
        }
        if (view instanceof EditText) {
            filter.add(view);
        } else if (view instanceof ViewGroup) {
            final ViewGroup vg = ((ViewGroup) view);
            final int count = vg.getChildCount();
            for (int i = 0; i < count; i++) {
                filterView(vg.getChildAt(i));
            }
        }
    }

    private int getScrollY(AbsListView view) {
        View c = view.getChildAt(0);
        if (c == null) {
            return 0;
        }
        int firstVisiblePosition = view.getFirstVisiblePosition();
        int top = c.getTop();
        return -top + firstVisiblePosition * c.getHeight();
    }

    /**
     * Here we should change it's enabled when we try to pull the view.
     *
     * @param pulling during pulling ?
     */
    private void onPulling(boolean pulling) {
        if (tag != null) {
            /**
             * here try to cancel the select
             */
            //tag.setChoiceMode(pulling ? AbsListView.CHOICE_MODE_NONE : AbsListView.CHOICE_MODE_SINGLE);
            tag.setEnabled(!pulling);
        }
    }

    private void updateHeadView(float offset) {
        if (headView == null) return;
        /**
         * from -headHeight  -> ratio * headHeight - headHeight -> 0 -> -headHeight
         *
         */
        if (status == PULLING) {
            headView.setPadding(0, (int) offset, 0, 0);
            /**
             * -headHeight  -> ratio * headHeight
             *  rotation : 0 -> 720
             *  alpha: 0 -> 1
             */
            final float aspect = (offset + headHeight) / (ratio * headHeight);
            tip.setRotation(720 * aspect);
            tip.setAlpha(aspect);
        } else if (status == REFRESH) {
            /**
             * ratio * headHeight - headHeight -> 0
             * rotation: 720 -> 720
             * alpha: 1 -> 1
             * scale: 1 -> 1
             */
            headView.setPadding(0, (int) offset, 0, 0);
        } else if (status == WAITING) {
            /**
             * ratio *  0 -> -headHeight
             * rotation: 720 -> 360
             * alpha:  1 -> 0
             * scale: 1 -> 0
             */
            tip.setRotation(720 - 360 * offset);
            tip.setAlpha(offset);
            tip.setScaleX(offset);
            tip.setScaleY(offset);
            if (offset <= 0) {
                //reset
                status = IDLE;
                headView.setPadding(0, -headHeight, 0, 0);
                tip.setRotation(0);
                tip.setAlpha(0);
                tip.setScaleX(1);
                tip.setScaleY(1);
            }
        }
    }
}
